I was recently annoyed by a "dimmable" LED bulb I bought, which had some nasty flickering to it at start-up. I also thought there was a weird flickering quality to it at stead-state, but wasn't sure. To test this, I built a quick little photocell sensor, hooked it up to an Arduino clone, and captured data in IPython. From there, a quick bit of signal analysis and we can see the truth!
While all this was hooked up, I took the opportunity to check out incandescent bulbs, a little flicker LED candle, and a few other things (the others were pretty dull, so they're not included here).
The data was captured using a bog-standard "Arduino"*, using a quick voltage divider with a photocell. If you want to reproduce this, you'll want to experiment to get things just right for you. Photocells aren't the most precise things in the universe, and your lighting levels may vary from the range I wanted to measure.
For mine, I have:
+5V ---- [ photocell ] --- ADC0 ---- [ 1k ohm ] ---- GND
The code for the Arduino is pretty simple:
// -*- c++ -*- void setup() { analogReference(INTERNAL); Serial.begin(9600); pinMode(13, OUTPUT); } void loop() { while(1) { digitalWrite(13, 1); uint8_t i = analogRead(0); Serial.write(i); digitalWrite(13, 0); } }
If you want "precise" ** frequency measurements, you'll want to hook an oscilliscope up to D13 and measure your sampling interval. For my hardware, it was 962.1Hz, which I'm calling 962Hz for everything here. If your chip is clocked at 16MHz as well, it will likely run the same speed. This is convenient, as 9600 baud can't support much faster sample rates.
* Actually an Arduino clone I designed and used to sell as kits. ** "Precise" isn't quite the right word here, but, hey.
This is also pretty simple. There's another notebook adjacent to this one which contains the DAQ I used. But, here's the quick version:
RUN_NAME="foo" import serial s = serial.Serial("/dev/tty.usbserial-A4006Dx4", 9600) N_SAMPLES=Fs*10 for i in range(200): s.read() # discard the head to discharge things data = [ ord(s.read()) for _ in range(N_SAMPLES) ] s.close() np.save("%s.npy" % RUN_NAME, data) figure(figsize=(20,5)) plot(data) len(data)
It really is that simple, assuming you have the serial (aka 'pyserial') library installed.
# Just ignore this block unless you want to see the gory details
%pylab inline
import scipy
import scipy.signal
import scipy.fftpack
import numpy as np
Fs=962 # Sample rate, Hz
def show_fft(Fs, y):
"""Show the FFT of a data sample, in the current figure"""
# sample spacing
T = 1.0 / Fs
# Number of samplepoints
N = len(y)
w = scipy.signal.blackman(N)
ywf = scipy.fftpack.fft(y*w)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
axvline(60, color="#ffcccc")
axvline(120, color="#ccccff")
semilogy(xf[1:N/2], 2.0/N * np.abs(ywf[1:N/2]), '-r')
xticks([30,60,120,180,240])
xlim(0,250)
def show_fft_and_plot(Fs, y):
"""Convenience method for making an FFT/time-series plot pair"""
figure(figsize=(20,5))
subplot(1,2,1)
show_fft(Fs,y)
ylim(10**-3, 10**1.5)
subplot(1,2,2)
plot(y)
It seems appropriate to start off with the spectra from the data:
disps = [ ("incandescent.npy", "Incandescent bulb"), ("dimmable_led_steady.npy", "Dimmable LED, steady-state"), ("dimmable_led_steady_dimmed.npy", "Dimmable LED, stead-state, dimmed"), ("dimmable_led.npy", "Dimmable LED, annoying flickering state") ]
len(disps)
figure(figsize=(20,10))
for i, dpdn in enumerate(disps):
d_path, d_name = dpdn
subplot(len(disps)/2, 2, i+1)
show_fft(Fs, np.load(d_path)[1000:1500])
ylim(10**-4, 10**2)
title(d_name)
Before we get into the weird stuff, let's start with a really boring incandescent bulb.
data = np.load("incandescent.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, incandescen bulb")
show_fft_and_plot(Fs, data[2000:2500])
data = np.load("dimmable_led_steady.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, steady-state")
show_fft_and_plot(Fs, data[2000:2500])
title("foo")
data = np.load("dimmable_led_steady_dimmed.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, steady-state (dimmed)")
show_fft_and_plot(Fs, data[2000:2500])
title("foo")
data = np.load("dimmable_led.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, annoying-state")
show_fft_and_plot(Fs, data[2000:2500])
title("foo")
This is mostly for amusement, but: here's the crazy flickering candle LED
data = np.load("candle.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, candle LED")
show_fft_and_plot(Fs, data[10000:30000])
title("foo")